//-----------------------------------------------------------------------------
// Filename: Digest.cs
//
// Description: Implements Digest Authentication as defined in RFC2617.
//
// History:
// 08 Sep 2005	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using mimosa.SIP;

#if UNITTEST
using NUnit.Framework;
#endif

namespace mimosa.Authentication
{
	public class AuthorizationRequest
	{
		public const string AUTH_ALGORITHM = "md5";
		
		public string Realm;
		public string Username;
		public string Password;
		public string DestinationURL;
		public string URI;
		public string Nonce;
		public string RequestType;
		public string Response;

		public string Digest
		{
			get
			{
				return HTTPDigest.DigestCalcResponse(
					AUTH_ALGORITHM,
					Username,
					Realm,
					Password,
					URI,
					Nonce,
					m_nonceCount,
					null,
					null,
					RequestType,
					null,
					null);
			}
		}
		

		private int m_nonceCount = 0;
		//private string m_password;	

		public AuthorizationRequest()
		{}


		public AuthorizationRequest(string authRequestStr)
		{
			if(Regex.Match(authRequestStr, AuthHeaders.AUTH_REALM_KEY, RegexOptions.IgnoreCase).Success)
			{
				Realm = Regex.Match(authRequestStr, AuthHeaders.AUTH_REALM_KEY + @"=""?(?<value>[^""]*)""?(,|\s|$)").Result("${value}");
			}

			if(Regex.Match(authRequestStr, AuthHeaders.AUTH_NONCE_KEY, RegexOptions.IgnoreCase).Success)
			{
				Nonce = Regex.Match(authRequestStr, AuthHeaders.AUTH_NONCE_KEY + @"=""?(?<value>[^""]*)""?(,|\s|$)").Result("${value}");
			}
		}

		public AuthorizationRequest(string realm, string username, string password, string uri, string nonce, string request)
		{
			Realm = realm;
			Username = username;
			Password = password;
			URI = uri;
			Nonce = nonce;
			RequestType = request;
		}

		public override string ToString()
		{
			//return "Digest username=\"" + Username + "\",realm=\"" + Realm + "\",nonce=\"" + Nonce + "\",uri=\"" + URI + "\",algorithm=\"" + AuthAlgorithm + "\",response=\"" + Digest  + "\"\n";
			return "Digest username=\"" + Username + "\",realm=\"" + Realm + "\",nonce=\"" + Nonce + "\",response=\"" + Digest  + "\",uri=\"" + URI + "\",algorithm=" + AUTH_ALGORITHM;
		}

		#region Unit testing.

		#if UNITTEST
	
		[TestFixture]
		public class SIPResponseUnitTest
		{
			[TestFixtureSetUp]
			public void Init()
			{
				
			}

			[TestFixtureTearDown]
			public void Dispose()
			{			
				
			}

			[Test]
			public void SampleTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				Assert.IsTrue(true, "True was false.");

				Console.WriteLine("-----------------------------------------");
			}

			[Test]
			public void KnownDigestTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("asterisk", "aaronxten2", "password", "sip:303@bluesipd", "17190028", "INVITE");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue(true, "True was false.");

				Console.WriteLine("-----------------------------------------");
			}

			[Test]
			public void KnownDigestTestObscureChars()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("sip.blueface.ie", "aaronnetgear", "!\"$%^&*()_-+=}[{]~#@':;?><,.", "sip:sip.blueface.ie:5060", "1430352056", "REGISTER");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue(digest == "500fd998b609a0f24b45edfe190f2a17", "The digest was incorrect.");

				Console.WriteLine("-----------------------------------------");
			}

			[Test]
			public void KnownDigestTestObscureChars2()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("sip.blueface.ie", "aaronxten", "_*!$%^()\"", "sip:sip.blueface.ie", "1263192143", "REGISTER");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue(digest == "54b08b70ed1976068b9e18d38ea59849", "The digest was incorrect.");

				Console.WriteLine("-----------------------------------------");
			}


			[Test]
			public void KnownDigestTest2()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("asterisk", "aaronxten2", "password", "sip:303@213.168.225.133", "4a4ad124", "INVITE");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue(true, "True was false.");

				Console.WriteLine("-----------------------------------------");
			}

			[Test]
			public void KnownRegisterDigestTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("asterisk", "aaron", "password", "sip:blueface", "1c8192c9", "REGISTER");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue("08881d1d56c0b21f11d19f4067da7045" == digest, "Digest was incorrect.");

				Console.WriteLine("-----------------------------------------");
			}

		
			[Test]
			public void KnownRegisterDigestTest2()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				AuthorizationRequest authRequest = new AuthorizationRequest("asterisk", "aaron", "password", "sip:blueface", "1c3c7a97", "REGISTER");

				string digest = authRequest.Digest;

				Console.WriteLine("Digest = " + digest + ".");
				Console.WriteLine(authRequest.ToString());

				Assert.IsTrue("1ef20beed71043225873e4f6712e4922" == digest, "Digest was incorrect.");

				Console.WriteLine("-----------------------------------------");
			}
		}

		#endif

		#endregion
	}


	public class AuthorizationResponse
	{
		
	}

	public class HTTPDigest
	{
		/// <summary>
		/// Calculate H(A1) as per HTTP Digest specification.
		/// </summary>
		public static string DigestCalcHA1(
			string username,
			string realm,
			string password
			)
		{
			string A1 = String.Format("{0}:{1}:{2}", username, realm, password);
			
			return GetMD5HashBinHex(A1);
		}

		/// <summary>
		/// Calculate H(A2) as per HTTP Digest specification.
		/// </summary>
		public static string DigestCalcHA2(
			string method,
			string uri)
		{
			string A2 = String.Format("{0}:{1}", method, uri);
			
			return GetMD5HashBinHex(A2);
		}

		public static string DigestCalcResponse(
			string algorithm,
			string username,
			string realm,
			string password,
			string uri,		
			string nonce,
			int nonceCount,
			string cnonce,
			string qop,			// qop-value: "", "auth", "auth-int".
			string method,
			string digestURL,
			string hEntity
			)
		{
			string HA1 = DigestCalcHA1(username, realm, password);
			string HA2 = DigestCalcHA2(method, uri);
			
			string unhashedDigest = String.Format("{0}:{1}:{2}",
				HA1,
				nonce,
				HA2);

			return GetMD5HashBinHex(unhashedDigest);
		}

		public static string GetMD5HashBinHex(string val)
		{
			Encoding enc = new ASCIIEncoding();
			MD5 md5 = new MD5CryptoServiceProvider();
			byte[] bHA1 = md5.ComputeHash(enc.GetBytes(val));
			string HA1 = null;
			for (int i = 0 ; i < 16 ; i++)
				HA1 += String.Format("{0:x02}",bHA1[i]);
			return HA1;
		}


		#region Unit testing.

		#if UNITTEST
	
		[TestFixture]
		public class DigestUnitTest
		{
			[TestFixtureSetUp]
			public void Init()
			{
				
			}

			[TestFixtureTearDown]
			public void Dispose()
			{			
				
			}

			[Test]
			public void SampleTest()
			{
				Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				Assert.IsTrue(true, "True was false.");
			}

		}

		#endif

		#endregion
	}
}
